// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.WindowsAPICodePack.DirectX.Direct3D10;
using Microsoft.WindowsAPICodePack.DirectX.DXGI;

namespace Microsoft.WindowsAPICodePack.DirectX.DirectXUtilities
{
    public class PlyMesh : IRender
    {
        #region IRender Members

        public void PrepareRender( D3DDevice device, PassDescription passDescription )
        {
            // set input layout
            vertexLayout = device.CreateInputLayout( 
                inputLayout,
                passDescription.pIAInputSignature,
                passDescription.IAInputSignatureSize );
            
            device.IA.SetInputLayout( vertexLayout );

            //  set up vertex buffer and index buffer
            uint stride = (uint)Marshal.SizeOf( typeof( Vector3F ) );
            uint offset = 0;
            device.IA.SetVertexBuffers( 0, new D3DBuffer[ ] { VertexBuffer }, new uint[ ] { stride }, new uint[ ] { offset } );
            device.IA.SetIndexBuffer( IndexBuffer, Format.R32_UINT, 0 );

            // Set primitive topology
            device.IA.SetPrimitiveTopology( PrimitiveTopology.Trianglelist );
        }

        public void Render( Microsoft.WindowsAPICodePack.DirectX.Direct3D10.D3DDevice device )
        {
            device.DrawIndexed( (uint)VertexIndicies, 0, 0 );
        }

        #endregion

        #region D3D data
        /// <summary>
        /// The layout of the vertex data
        /// </summary>
        InputElementDescription[ ] inputLayout =
            new InputElementDescription[ ]
            {
                new InputElementDescription()
                {
                    SemanticName = "POSITION",
                    SemanticIndex = 0,
                    ElementDataFormat = Format.R32G32B32_FLOAT,
                    InputSlot = 0,
                    AlignedByteOffset = 0,
                    InputSlotClass = InputClassification.PerVertexData,
                    InstanceDataStepRate = 0,
                },
                new InputElementDescription()
                {
                    SemanticName = "TEXCOORD",
                    SemanticIndex = 0,
                    ElementDataFormat = Format.R32G32B32A32_FLOAT,
                    InputSlot = 0,
                    AlignedByteOffset = 12,
                    InputSlotClass = InputClassification.PerVertexData,
                    InstanceDataStepRate = 0,
                }
            };

        /// <summary>
        /// The verticies to render
        /// </summary>
        public D3DBuffer VertexBuffer;

        /// <summary>
        /// The indicies of the triangles in the VertexBuffer
        /// </summary>
        public D3DBuffer IndexBuffer;

        /// <summary>
        /// The number of verticies in the index buffer
        /// </summary>
        public int VertexIndicies;
        #endregion

        #region Implementation
        enum CoordinateType
        {
            // x coordinate of vertex
            x,

            // y coordinate of vertex
            y,

            // z coordinate of vertex
            z,
        
            // x component of normal vector
            nx,

            // y component of normal vector
            ny,

            // z component of normal vector
            nz,


            // u texture component
            s,

            // y texture component
            t
        }
        
        
        /// <summary>
        /// Loads a PLY mesh from a text file
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static PlyMesh FromFile( string path, D3DDevice device )
        {
            StreamReader plyFile = File.OpenText( path );

            // parse header
            int verticies = 0;
            int faces = 0;
            string elementVertex = "element vertex";
            string elementFace = "element face";
            string elementComment = "comment";
            string elementProperty = "property";

            string headerLine = plyFile.ReadLine( );
            if( headerLine != "ply" )
            {
                throw new System.IO.InvalidDataException( "not a ply file" );
            }

            Dictionary<CoordinateType, int> coordinateMap = new Dictionary<CoordinateType, int>( );
            int coordinateIndex = 0;

            do
            {
                headerLine = plyFile.ReadLine( );
                
                // verify that this is an ascii ply
                if( headerLine.StartsWith( "format" ) &&
                    headerLine.EndsWith( "ascii 1.0" ) == false )
                {
                    throw new System.IO.InvalidDataException( "only ascii ply files are supported" );
                }

                // skip comments
                if( headerLine.StartsWith( elementComment ) )
                {
                    continue;
                }

                // get vertex count
                if( headerLine.StartsWith( elementVertex ) )
                {
                    verticies = Int32.Parse(
                        headerLine.Remove( 0, elementVertex.Length + 1 ) );
                }

                // get face count
                if( headerLine.StartsWith( elementFace ) )
                {
                    faces = Int32.Parse(
                        headerLine.Remove( 0, elementFace.Length + 1 ) );
                }

                // map coordinate index
                if( headerLine.StartsWith( elementProperty ) )
                {
                    string[ ] propertyParts = headerLine.Split( new char[ ] { ' ' } );
                    string coordinate = propertyParts[ 2 ];
                    if( typeof( CoordinateType ).GetField( coordinate ) != null )
                    {
                        coordinateMap[ (CoordinateType)Enum.Parse( typeof( CoordinateType ), coordinate ) ] = coordinateIndex;
                        coordinateIndex++;
                    }
                }
            }
            while( headerLine != "end_header" );

            // verify that we have required coordinate mappings
            if( coordinateMap.ContainsKey(CoordinateType.x) == false ||
                coordinateMap.ContainsKey(CoordinateType.y) == false ||
                coordinateMap.ContainsKey(CoordinateType.z) == false )
            {
                throw new System.IO.InvalidDataException( "header data invalid" );
            }


            // build vertex list
            Vector3F[ ] vertex = new Vector3F[ verticies ];
            for( int vertexIndex = 0; vertexIndex < verticies; vertexIndex++ )
            {
                string vertexLine = plyFile.ReadLine( );
                vertexLine = vertexLine.TrimEnd( new char[ ] { ' ' } );
                string[ ] components = vertexLine.Split( new char[ ] { ' ' } );

                vertex[ vertexIndex ].x = float.Parse( components[ coordinateMap[CoordinateType.x] ] );
                vertex[ vertexIndex ].y = float.Parse( components[ coordinateMap[CoordinateType.y] ] );
                vertex[ vertexIndex ].z = float.Parse( components[ coordinateMap[CoordinateType.z] ] );
            }

            // TODO: handle normals and texture map

            // build vertex index list
            List<Int32> triangleIndicies = new List<Int32>( );
            for( int faceIndex = 0; faceIndex < faces; faceIndex++ )
            {
                string faceLine = plyFile.ReadLine( );
                faceLine = faceLine.TrimEnd( new char[ ] { ' ' } );
                string[ ] components = faceLine.Split( new char[ ] { ' ' } );
                int indicies = Int32.Parse( components[ 0 ] );
                if( indicies != components.Length - 1 )
                    throw new System.IO.InvalidDataException( "invalid index data" );

                for( int triangleFace = 0; triangleFace < indicies - 2; triangleFace++ )
                {
                    // Breaking complex polygons into triangles, preserving winding order.
                    triangleIndicies.Add( Int32.Parse( components[ 1 ] ) );
                    triangleIndicies.Add( Int32.Parse( components[ 2 + triangleFace ] ) );
                    triangleIndicies.Add( Int32.Parse( components[ 3 + triangleFace ] ) );
                }
            }


            return new PlyMesh( vertex, triangleIndicies, device );
        }

        /// <summary>
        /// Creates the model's one-and-only part from raw vertex data
        /// </summary>
        /// <param name="vertexData"></param>
        /// <param name="indicies"></param>
        private PlyMesh( Vector3F[ ] vertexData, List<Int32> indicies, D3DDevice device )
        {
            IntPtr plyVertex = Marshal.AllocHGlobal(
                Marshal.SizeOf( typeof( Vector3F ) ) * vertexData.Length );
            float[] allVerts = new float[ vertexData.Length * 3 ];
            for( int vertIndex = 0; vertIndex < vertexData.Length; vertIndex++ )
            {
                allVerts[ vertIndex * 3 + 0 ] = vertexData[ vertIndex ].x;
                allVerts[ vertIndex * 3 + 1 ] = vertexData[ vertIndex ].y;
                allVerts[ vertIndex * 3 + 2 ] = vertexData[ vertIndex ].z;
            }
            Marshal.Copy( allVerts, 0, plyVertex, allVerts.Length );

            IntPtr plyIndex = Marshal.AllocHGlobal( 
                Marshal.SizeOf( typeof(Int32) ) * indicies.Count ) ;
            Marshal.Copy( indicies.ToArray(), 0, plyIndex, indicies.Count );

            BufferDescription bdv = new BufferDescription( )
            {
                BufferUsage = Usage.Default,
                ByteWidth = (uint)(Marshal.SizeOf( typeof(Vector3F) ) * vertexData.Length),
                BindFlags = BindFlag.VertexBuffer,
                CPUAccessFlags = 0,
                MiscFlags = 0
            };
            SubresourceData vertexInit = new SubresourceData( )
            {
                SysMem = plyVertex
            };


            BufferDescription bdi = new BufferDescription( )
            {
                BufferUsage = Usage.Default,
                ByteWidth = (uint)(indicies.Count * sizeof( Int32 )) ,
                BindFlags = BindFlag.IndexBuffer,
                CPUAccessFlags = 0,
                MiscFlags = 0
            };
            SubresourceData indexInit = new SubresourceData( )
            {
                SysMem = plyIndex
            };

            VertexBuffer = device.CreateBuffer( bdv, vertexInit );
            IndexBuffer = device.CreateBuffer( bdi, indexInit );
            VertexIndicies = indicies.Count;

            Marshal.FreeHGlobal( plyVertex );
            Marshal.FreeHGlobal( plyIndex );
        }

        InputLayout vertexLayout;

        #endregion
    }
}
